package mymiddleware

import (
	"context"
	"errors"
	"gf-xb-api/cwt/result"
	"gf-xb-api/internal/consts"
	"gf-xb-api/internal/model"
	"gf-xb-api/internal/model/dto"
	"gf-xb-api/internal/model/entity"
	"gf-xb-api/internal/service"
	"gf-xb-api/utility/xjwt"
	"gf-xb-api/utility/xpointer"
	"gf-xb-api/utility/xredis"
	"gf-xb-api/utility/xstr"
	"net/http"
	"time"

	jwt "github.com/gogf/gf-jwt/v2"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gcache"
	gojwt "github.com/golang-jwt/jwt/v4"
)

var authService *jwt.GfJWTMiddleware
var timeoutSeconds int

func Auth() *jwt.GfJWTMiddleware {
	return authService
}

func init() {
	var ctx = context.Background()

	var cache = gcache.New()
	cache.SetAdapter(gcache.NewAdapterRedis(g.Redis()))

	timeoutSeconds = g.Config().MustGet(ctx, consts.JWT_TIMEOUT).Int()

	auth := jwt.New(&jwt.GfJWTMiddleware{
		Realm:            g.Config().MustGet(ctx, consts.JWT_REALM).String(),                                // 用于展示中间件的名称
		SigningAlgorithm: g.Config().MustGet(ctx, consts.JWT_SIGNINGALGORITHM).String(),                     // 签名算法
		Key:              []byte(g.Config().MustGet(ctx, consts.JWT_KEY).String()),                          // token过期时间密钥
		Timeout:          time.Second * time.Duration(timeoutSeconds),                                       // token过期时间
		MaxRefresh:       time.Second * time.Duration(g.Config().MustGet(ctx, consts.JWT_MAXREFRESH).Int()), // token刷新时间
		IdentityKey:      g.Config().MustGet(ctx, consts.JWT_IDENTITYKEY).String(),                          // 身份验证的key值
		TokenLookup:      "header: Authorization, query: token, cookie: jwt",                                // token检索模式，用于提取token-> Authorization
		TokenHeadName:    g.Config().MustGet(ctx, consts.JWT_TOKENHEADNAME).String(),                        // token在请求头时的名称，默认值为Bearer;// 客户端在header中传入Authorization 对一个值是Bearer + 空格 + token
		TimeFunc:         time.Now,                                                                          // 测试或服务器在其他时区可设置该属性
		Authenticator:    Authenticator,                                                                     // 根据登录信息对用户进行身份验证的回调函数
		Unauthorized:     Unauthorized,                                                                      // 处理不进行授权的逻辑
		PayloadFunc:      PayloadFunc,                                                                       // 登录期间的回调的函数
		IdentityHandler:  IdentityHandler,                                                                   // 解析并设置用户身份信息
		Authorizator:     Authorizator,
		BlacklistPrefix:  consts.JWT_TOKEN_BLACKLIST_REDISKEY, // 黑名单Key
		CacheAdapter:     cache.GetAdapter(),
	})
	authService = auth
}

// PayloadFunc is a callback function that will be called during login.
// Using this function it is possible to add additional payload data to the webtoken.
// The data is then made available during requests via c.Get("JWT_PAYLOAD").
// Note that the payload is not encrypted.
// The attributes mentioned on jwt.io can't be used as keys for the map.
// Optional, by default no additional data will be set.
func PayloadFunc(data interface{}) jwt.MapClaims {
	claims := jwt.MapClaims{}
	params := data.(map[string]interface{})
	if len(params) > 0 {
		for k, v := range params {
			claims[k] = v
		}
	}
	return claims
}

// IdentityHandler get the identity from JWT and set the identity for every request
// Using this function, by r.GetParam("id") get identity
func IdentityHandler(ctx context.Context) interface{} {
	claims := jwt.ExtractClaims(ctx)
	// return claims[authService.IdentityKey]
	return claims
}

// Authorizator Callback function that should perform the authorization of the authenticated user. Called only after an authentication success. Must return true on success, false on failure. Optional, default to success.
func Authorizator(data interface{}, ctx context.Context) bool {

	// 检查是否已失效
	claims, ok := data.(jwt.MapClaims)
	if !ok {
		return false
	}

	nonce := (claims[consts.JWT_INFO_NONCE]).(string)
	uuid := (claims[consts.JWT_INFO_UUID]).(string)
	deviceType := int((claims[consts.JWT_INFO_DEVICETYPE]).(float64))

	// 校验Redis中存储的token信息
	redisKey := xredis.JwtTokenKey(uuid, deviceType)

	redisVal, err := g.Redis().Get(ctx, redisKey)
	if err != nil {
		return false
	}

	jwtToken, err := xjwt.Parse(redisVal.String(), authService.SigningAlgorithm, string(authService.Key))
	if err != nil {
		return false
	}

	redisClaims := jwtToken.Claims.(gojwt.MapClaims)
	redisNonce := (redisClaims[consts.JWT_INFO_NONCE]).(string)

	if nonce != redisNonce {
		return false
	}

	isSuperAdmin := int((claims[consts.JWT_INFO_IS_SUPERADMIN]).(float64))

	r := g.RequestFromCtx(ctx)

	fUserId := (claims[authService.IdentityKey]).(float64)
	userId := uint(fUserId)

	r.SetCtxVar(authService.IdentityKey, userId)
	r.SetCtxVar(consts.JWT_INFO_UUID, uuid)
	r.SetCtxVar(consts.JWT_INFO_DEVICETYPE, deviceType)
	r.SetCtxVar(consts.JWT_INFO_IS_SUPERADMIN, isSuperAdmin)

	// 超级管理员可以访问所有
	if isSuperAdmin == 1 {
		return true
	}

	// 其他角色需要有访问api的权限才可以
	apiMethod := consts.GetHttpMethod(r.Method)
	if apiMethod.String() == "NONE" {
		// 我们只支持GET,POST,PUT和DELETE
		return false
	}

	canAccess, _ := service.User().CanAccessApi(ctx, &dto.UserCanAccessApi{
		UserId:    userId,
		ApiRoute:  r.URL.Path,
		ApiMethod: uint(apiMethod),
	})

	return canAccess
}

// Unauthorized is used to define customized Unauthorized callback function.
func Unauthorized(ctx context.Context, code int, message string) {
	r := g.RequestFromCtx(ctx)

	if consts.JWT_ERRFAILEDAUTHENTICATION != message && consts.JWT_ErrLoginInactiveMsg != message {
		message = "未授权或已在他处登录"
	}

	r.Response.Status = http.StatusUnauthorized
	r.Response.WriteJson(g.Map{
		"resultType": result.CodeNotAuthorized.ResultType(),
		"succeeded":  false,
		"error":      true,
		"message":    message, // "未授权或已在他处登录", // message,
	})
	r.ExitAll()
}

// Authenticator is used to validate login parameters.
// It must return user data as user identifier, it will be stored in Claim Array.
// if your identityKey is 'id', your user data must have 'id'
// Check error (e) to determine the appropriate error message.
// 此方法用于验证账户密码是否正确，基于自己的代码嵌套即可
func Authenticator(ctx context.Context) (interface{}, error) {
	var (
		r  = g.RequestFromCtx(ctx)
		in model.LoginInput
	)
	// if err := r.Parse(&in); err != nil {
	// 	return "", err
	// }

	userInfo := r.GetCtxVar(consts.JWT_INFO_USER)
	if userInfo.Val() == nil {
		return nil, errors.New(consts.JWT_ERRFAILEDAUTHENTICATION)
	}

	user, ok := userInfo.Val().(*entity.SysUser)
	if !ok {
		return nil, errors.New(consts.JWT_ERRFAILEDAUTHENTICATION)
	}

	// user, err := service.Membership().Login(ctx, &in)
	// if err != nil {
	// 	return nil, err // errors.New(consts.JWT_ERRFAILEDAUTHENTICATION)
	// }

	// 检查当前设备类型是否已登录，将已登录的给登出掉
	nonce := xstr.GetUUID()

	isSuperAdmin := user.IsSuperadmin
	if isSuperAdmin == nil {
		isSuperAdmin = xpointer.GetPointer(0)
	}

	infoMap := map[string]interface{}{
		authService.IdentityKey:       user.Id,
		consts.JWT_INFO_UUID:          user.Uuid,
		consts.JWT_INFO_DEVICETYPE:    in.DeviceType,
		consts.JWT_INFO_NONCE:         nonce,
		consts.JWT_INFO_IS_SUPERADMIN: isSuperAdmin,
	}

	// context var
	r.SetCtxVar(authService.IdentityKey, user.Id)
	r.SetCtxVar(consts.JWT_INFO_UUID, user.Uuid)
	r.SetCtxVar(consts.JWT_INFO_DEVICETYPE, in.DeviceType)

	/*// 保存在Redis中
	redisKey := xredis.JwtTokenKey(user.Uuid, in.DeviceType)

	expiry := gconv.PtrInt64(timeoutSeconds)
	g.Redis().Set(ctx, redisKey, nonce, gredis.SetOption{
		TTLOption: gredis.TTLOption{
			EX: expiry,
		},
	})*/

	return infoMap, nil
}
